package la.iok.hzsvn.lewin.movie.cloud.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import la.iok.hzsvn.lewin.movie.cloud.config.RedisKey;
import la.iok.hzsvn.lewin.movie.cloud.config.SmsConfig;
import la.iok.hzsvn.lewin.movie.cloud.entity.Device;
import la.iok.hzsvn.lewin.movie.cloud.entity.User;
import la.iok.hzsvn.lewin.movie.cloud.model.PhoneCode;
import la.iok.hzsvn.lewin.movie.cloud.model.PhoneCodeParam;
import la.iok.hzsvn.lewin.movie.cloud.utils.PhoneValidateUtils;
import la.iok.hzsvn.lewin.movie.core.exception.ErrorCode;
import la.iok.hzsvn.lewin.movie.core.model.QR;
import la.iok.hzsvn.redis.service.RedisService;
import la.iok.hzsvn.share.annotations.NotNull;
import la.iok.hzsvn.share.annotations.Nullable;
import la.iok.hzsvn.share.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static la.iok.hzsvn.lewin.movie.core.exception.ErrorCode.*;

@Service
public class PhoneCodeServiceImpl implements PhoneCodeService{
    private static final Logger logger = LoggerFactory.getLogger(PhoneCodeService.class);
    private static final String SMS_APP_URL = "https://139.9.32.119:18312/common/sms/sendTemplateMessage";

    private final RedisKey redisKey;
    private final PhoneCodeGenerator phoneCodeGenerator;
    private final DeviceService deviceService;
    private final RedisService redisService;
    private final ObjectMapper objectMapper;
    private final SmsConfig.SmsProperties smsProperties;
    private final QRCodeService qrCodeService;

    public PhoneCodeServiceImpl(RedisKey redisKey,
                                PhoneCodeGenerator phoneCodeGenerator,
                                DeviceService deviceService,
                                RedisService redisService,
                                ObjectMapper objectMapper,
                                SmsConfig.SmsProperties smsProperties,
                                QRCodeService qrCodeService) {
        this.redisKey = redisKey;
        this.phoneCodeGenerator = phoneCodeGenerator;
        this.deviceService = deviceService;
        this.redisService = redisService;
        this.objectMapper = objectMapper;
        this.smsProperties = smsProperties;
        this.qrCodeService = qrCodeService;
    }

    @Override
    public PhoneCode generateAndSavePhoneCode(PhoneCodeParam param) {
        return generateAndSavePhoneCode(null,param);
    }

    @Override
    public PhoneCode generateAndSavePhoneCode(@Nullable User user, PhoneCodeParam param) {
        ErrorCode.OPERATION_TYPE_ERROR.assertTrue(param.operationReg() || param.operationLogin()
                || param.operationModifyAccount() || param.operationModifyPassword());
        String phoneNum = PHONE_NUMBER_ERROR.assertNotBlank(param.getPhoneNumber());
        PHONE_NUMBER_ERROR.assertTrue(PhoneValidateUtils.validatePhoneNumber(phoneNum));

        Long orgUnitId = null;
        boolean byToken = StringUtils.isBlank(param.getCode()) && StringUtils.isBlank(param.getUuid());
        if(byToken){
            PROPERTY_CHECK_ERROR.assertNotNull(user,"用户","用户必须登录或者使用设备码调用");
            orgUnitId = user.getOrgUnitId();
        }else{
            Device device = deviceService.select(param.getCode());
            if(device == null){
                if(StringUtils.isNotBlank(param.getUuid())){
                    DEVICE_CODE_EMPTY_ERROR.assertNotBlank(param.getCode());
                    QR qr = qrCodeService.qrCode(param.getUuid());
                    QR_TYPE_ERROR.assertTrue(qr.typePlayRecord(),"开本记录");
                    orgUnitId = -1L;
                }else{
                    ErrorCode.DEVICE_NOT_EXIST.assertNotNull(device,param.getCode());
                }
            }else{
                orgUnitId = device.getOrgUnitId();
            }
        }
        PhoneCode phoneCode = phoneCodeGenerator.fastGenerateCode();
        phoneCode.setPhoneNumber(phoneNum);
        phoneCode.setOperation(param.getOperation());
        phoneCode.setOrgUnitId(orgUnitId);
        Long time = redisService.getExpire(redisKey.phoneCodeKey(phoneNum),TimeUnit.SECONDS);
        RETRY_FAST_ERROR.assertTrue(time == null || time <= phoneCode.getExpiresIn());
        Object phoneNumber;
        if(byToken){
            phoneNumber = redisService.get(redisKey.userPhoneCodeKey(user.getId()));
        }else{
            phoneNumber = redisService.get(redisKey.devicePhoneCodeKey(param.getCode()));
        }
        RETRY_FAST_ERROR.assertTrue(phoneNumber == null || Objects.equals(phoneNumber,phoneNum));

        // 放入缓存
        redisService.hash(redisKey.phoneCodeKey(phoneNum),phoneCode);
        redisService.expire(redisKey.phoneCodeKey(phoneNum),phoneCode.getExpiresIn()* 2L,TimeUnit.SECONDS);
        if(byToken){
            redisService.set(redisKey.userPhoneCodeKey(user.getId()),phoneNum,phoneCode.getExpiresIn(),TimeUnit.SECONDS);
        }else{
            redisService.set(redisKey.devicePhoneCodeKey(param.getCode()),phoneNum,phoneCode.getExpiresIn(),TimeUnit.SECONDS);
        }
        return phoneCode;
    }

    @NotNull
    @Override
    public PhoneCode loadPhoneCode(@NotNull String phoneNumber) {
        PhoneCode phoneCode = redisService.hash(redisKey.phoneCodeKey(phoneNumber),PhoneCode.class);
        ErrorCode.PHONE_CODE_EXPIRED.assertNotNull(phoneCode);
        return phoneCode;
    }

    @Override
    public void sendSms(@NotNull PhoneCode phoneCode) {
        HttpsURLConnection connection = null;
        try {
            trustAllHttpsCertificates();
            SmsRequest request = new SmsRequest(smsProperties.getAccount(),smsProperties.getPassword(),
                    "SMS_22111600016",phoneCode);
            URL realUrl = new URL(SMS_APP_URL);
            connection = (HttpsURLConnection) realUrl.openConnection();
            connection.setHostnameVerifier((hostname, session) -> true);
            connection.setDoInput(true); // 设置可输入
            connection.setDoOutput(true); // 设置该连接是可以输出的
            connection.setRequestMethod("POST"); // 设置请求方式
            connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            // connection.connect();
            try(PrintWriter pw = new PrintWriter(new OutputStreamWriter(connection.getOutputStream(), StandardCharsets.UTF_8))){
                pw.write(objectMapper.writeValueAsString(request));
                pw.flush();
            }
            InputStream is;
            int status = connection.getResponseCode();
            if (200 == status) { // 200
                is = connection.getInputStream();
            } else { // 400/401
                is = connection.getErrorStream();
            }
            BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
            String line;
            StringBuilder result = new StringBuilder();
            while ((line = br.readLine()) != null) { // 读取数据
                result.append(line).append("\n");
            }
            SmsResponse res = objectMapper.readValue(result.toString(),SmsResponse.class);
            if(!res.success()){
                logger.error("发送验证码失败:{}",result);
            }
        } catch (NoSuchAlgorithmException | KeyManagementException | IOException e) {
            e.printStackTrace();
        } finally {
            if(connection != null){
                connection.disconnect();
            }
        }
    }

    static void trustAllHttpsCertificates() throws NoSuchAlgorithmException, KeyManagementException {
        TrustManager[] trustAllCerts = new TrustManager[] {
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) {
                    }


                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) {
                    }


                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                }
        };
        SSLContext sc = SSLContext.getInstance("TLSv1.2");
        sc.init(null, trustAllCerts, null);
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    }

    private static class SmsRequest {
        private String account;
        private String password;
        private List<SmsMessage> requestLists;

        public SmsRequest() {
        }

        public SmsRequest(@NotNull String account, @NotNull String password,@NotNull String templateId,@NotNull PhoneCode code) {
            this.account = account;
            this.password = password;
            this.requestLists = new ArrayList<>();
            this.requestLists.add(new SmsMessage(templateId,code));
        }

        public String getAccount() {
            return account;
        }

        public void setAccount(String account) {
            this.account = account;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        public List<SmsMessage> getRequestLists() {
            return requestLists;
        }

        public void setRequestLists(List<SmsMessage> requestLists) {
            this.requestLists = requestLists;
        }
    }
    private static class SmsMessage{
        private List<String> mobiles;
        private String templateId;
        private Map<String,String> templateParas;
        private String signature;

        public SmsMessage() {
        }

        /**
         * 构造消息对象
         * @param templateId 模板id
         * @param code 验证码
         */
        public SmsMessage(@NotNull String templateId,@NotNull PhoneCode code){
            this.mobiles = new ArrayList<>();
            this.mobiles.add(code.getPhoneNumber());
            this.templateId = templateId;
            templateParas = new HashMap<>();
            templateParas.put("code",code.getCode());
            templateParas.put("operation",code.operationStr());
            templateParas.put("duration",code.expiresMinuteStr());
            this.signature = "【盎宇宙全息剧本娱乐平台】";
        }
        public List<String> getMobiles() {
            return mobiles;
        }

        public void setMobiles(List<String> mobiles) {
            this.mobiles = mobiles;
        }

        public String getTemplateId() {
            return templateId;
        }

        public void setTemplateId(String templateId) {
            this.templateId = templateId;
        }

        public Map<String, String> getTemplateParas() {
            return templateParas;
        }

        public void setTemplateParas(Map<String, String> templateParas) {
            this.templateParas = templateParas;
        }

        public String getSignature() {
            return signature;
        }

        public void setSignature(String signature) {
            this.signature = signature;
        }
    }
    private static class SmsResponse {
        private String resultCode;
        private String resultDesc;

        public String getResultCode() {
            return resultCode;
        }

        public void setResultCode(String resultCode) {
            this.resultCode = resultCode;
        }

        public String getResultDesc() {
            return resultDesc;
        }

        public void setResultDesc(String resultDesc) {
            this.resultDesc = resultDesc;
        }
        public boolean success(){
            return "0".equals(resultCode);
        }
    }
}
